#ifndef PASSWORD_HASH_HPP
#define PASSWORD_HASH_HPP

#include "log.hpp"

#include <argon2.h>
#include <string>
#include <stdexcept>
#include <vector>
#include <random>
#include <fstream>
#include <iostream>

#define SALT_LENGTH 16 // 盐的长度
#define HASH_LENGTH 32 // 哈希值长度

#ifdef FOR_TEST                                                     // 测试时，源文件和主程序相对txt文件的路径不同
#define ADMIN_CREDENTIALS_FILE "../../source/admin_credentials.txt" // 存储盐值和哈希值的文件路径
#else
#define ADMIN_CREDENTIALS_FILE "../source/admin_credentials.txt" // 存储盐值和哈希值的文件路径
#endif

#define PREDEFINED_PASSWORD "admin" // 预设密码，用于第一次生成哈希

class PasswordHash
{
public:
    // 用于项目外部的密码验证接口
    static bool VerifyPassword(const std::string &password)
    {
        std::vector<uint8_t> salt, storedHash;

        // 第一次运行时，文件不存在，生成盐值和哈希值并存储
        if (!PasswordHash::readCredentialsFromFile(salt, storedHash))
        {
            LOG_INFO("首次运行，正在为管理员密码生成盐值和哈希值...");

            // 生成盐值
            salt = PasswordHash::generateSalt();

            // 使用预设密码生成哈希值
            auto hash = PasswordHash::hashPassword(PREDEFINED_PASSWORD, salt);

            // 保存盐值和哈希值到文件
            PasswordHash::saveCredentialsToFile(salt, hash);

            // 重新读取并更新盐值和哈希值
            PasswordHash::readCredentialsFromFile(salt, storedHash);
        }

        return PasswordHash::verifyPassword(password, salt, storedHash);
    }

public:
    // 生成随机盐
    static std::vector<uint8_t> generateSalt()
    {
        std::vector<uint8_t> salt(SALT_LENGTH);
        std::random_device rd;
        std::mt19937 gen(rd());
        std::uniform_int_distribution<uint8_t> dis(0, 255);

        for (auto &byte : salt)
        {
            byte = dis(gen);
        }
        return salt;
    }

    // 计算密码的 Argon2 哈希值
    static std::vector<uint8_t> hashPassword(const std::string &password, const std::vector<uint8_t> &salt)
    {
        std::vector<uint8_t> hash(HASH_LENGTH);

        int result = argon2i_hash_raw(
            3,       // 迭代次数
            1 << 16, // 内存使用量（64MB）
            1,       // 并发程度
            password.c_str(), password.size(),
            salt.data(), salt.size(),
            hash.data(), hash.size());

        if (result != ARGON2_OK)
        {
            throw std::runtime_error("Argon2 密码哈希计算失败");
        }

        return hash;
    }

    // 验证用户输入的密码
    static bool verifyPassword(const std::string &password, const std::vector<uint8_t> &salt, const std::vector<uint8_t> &storedHash)
    {
        // 重新计算用户输入的密码的哈希值
        auto calculatedHash = hashPassword(password, salt);

        // 比较计算出的哈希值与存储的哈希值
        return calculatedHash == storedHash;
    }

    // 将字节数组转换为十六进制字符串（方便存储）
    static std::string bytesToHex(const std::vector<uint8_t> &bytes)
    {
        static const char hex[] = "0123456789abcdef";
        std::string hexString;
        hexString.reserve(bytes.size() * 2);

        for (uint8_t byte : bytes)
        {
            hexString.push_back(hex[byte >> 4]);
            hexString.push_back(hex[byte & 0x0F]);
        }
        return hexString;
    }

    // 将十六进制字符串转换为字节数组
    static std::vector<uint8_t> hexToBytes(const std::string &hexString)
    {
        std::vector<uint8_t> bytes;
        bytes.reserve(hexString.size() / 2);

        for (size_t i = 0; i < hexString.size(); i += 2)
        {
            uint8_t byte = (hexToDigit(hexString[i]) << 4) | hexToDigit(hexString[i + 1]);
            bytes.push_back(byte);
        }
        return bytes;
    }

    // 从文件读取盐值和哈希值
    static bool readCredentialsFromFile(std::vector<uint8_t> &salt, std::vector<uint8_t> &storedHash)
    {
        std::ifstream file(ADMIN_CREDENTIALS_FILE, std::ios::in);
        if (!file.is_open())
        {
            return false; // 文件不存在
        }

        std::string saltHex, hashHex;
        file >> saltHex >> hashHex;

        salt = hexToBytes(saltHex);
        storedHash = hexToBytes(hashHex);

        file.close();
        return true;
    }

    // 将盐值和哈希值保存到文件
    static void saveCredentialsToFile(const std::vector<uint8_t> &salt, const std::vector<uint8_t> &hash)
    {
        std::ofstream file(ADMIN_CREDENTIALS_FILE, std::ios::out);
        if (!file.is_open())
        {
            throw std::runtime_error("无法打开文件以保存密码哈希和盐值");
        }

        file << bytesToHex(salt) << "\n"
             << bytesToHex(hash) << "\n";
        file.close();
    }

private:
    static uint8_t hexToDigit(char c)
    {
        if (c >= '0' && c <= '9')
            return c - '0';
        if (c >= 'a' && c <= 'f')
            return c - 'a' + 10;
        if (c >= 'A' && c <= 'F')
            return c - 'A' + 10;
        throw std::invalid_argument("Invalid hex character");
    }
};

#endif // PASSWORD_HASH_HPP
